/*
	Contains the input handling code

	Author: Aidan Goddard 22/7/14
*/

#include"Window.h"

//////////////////////////////////////////////////////////////
// user input mangling
void Snowstorm::Window::HandleUserInput(WPARAM wparam, LPARAM lparam)
{
	// get the keyboard input first
	char buffer[sizeof(RAWINPUT)];
	UINT size = sizeof(RAWINPUT);
	GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buffer, &size, sizeof(RAWINPUTHEADER));

	// extract keyboard raw input data
	RAWINPUT* raw = (RAWINPUT*)buffer;
	if (raw->header.dwType == RIM_TYPEKEYBOARD)
	{
		const RAWKEYBOARD& rawKB = raw->data.keyboard;
		UINT virtualKey = rawKB.VKey;
		UINT scanCode = rawKB.MakeCode;
		UINT flags = rawKB.Flags;
		
		if (virtualKey == 255)
		{
			// discard "fake keys" which are part of an escaped sequence
			return;
		}
		else if (virtualKey == VK_SHIFT)
		{
			// correct left-hand / right-hand SHIFT
			virtualKey = MapVirtualKey(scanCode, MAPVK_VSC_TO_VK_EX);
		}
		else if (virtualKey == VK_NUMLOCK)
		{
			// correct PAUSE/BREAK and NUM LOCK silliness, and set the extended bit
			scanCode = (MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC) | 0x100);
		}

		// e0 and e1 are escape sequences used for certain special keys, such as PRINT and PAUSE/BREAK.
		// see http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
		const bool isE0 = ((flags & RI_KEY_E0) != 0);
		bool isE1 = ((flags & RI_KEY_E1) != 0);
		
		if (isE1)
		{
			// for escaped sequences, turn the virtual key into the correct scan code using MapVirtualKey.
			// however, MapVirtualKey is unable to map VK_PAUSE (this is a known bug), hence we map that by hand.
			if (virtualKey == VK_PAUSE)
				scanCode = 0x45;
			else
				scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
		}
		
		switch (virtualKey)
		{
			// right-hand CONTROL and ALT have their e0 bit set
		case VK_CONTROL:
			if (isE0)
				virtualKey = VK_RCONTROL;
			else
				virtualKey = VK_LCONTROL;
			break;
 
		case VK_MENU:
			if (isE0)
				virtualKey = VK_RMENU;
			else
				virtualKey = VK_LMENU;
			break;
 
			// NUMPAD ENTER has its e0 bit set
		case VK_RETURN:
			if (isE0)
				virtualKey = VK_RETURN;
			break;
 
			// the standard INSERT, DELETE, HOME, END, PRIOR and NEXT keys will always have their e0 bit set, but the
			// corresponding keys on the NUMPAD will not.
		case VK_INSERT:
			if (!isE0)
				virtualKey = VK_NUMPAD0;
			break;
 
		case VK_DELETE:
			if (!isE0)
				virtualKey = VK_DECIMAL;
			break;
 
		case VK_HOME:
			if (!isE0)
				virtualKey = VK_NUMPAD7;
			break;
 
		case VK_END:
			if (!isE0)
				virtualKey = VK_NUMPAD1;
			break;
 
		case VK_PRIOR:
			if (!isE0)
				virtualKey = VK_NUMPAD9;
			break;
 
		case VK_NEXT:
			if (!isE0)
				virtualKey = VK_NUMPAD3;
			break;
 
			// the standard arrow keys will always have their e0 bit set, but the
			// corresponding keys on the NUMPAD will not.
		case VK_LEFT:
			if (!isE0)
				virtualKey = VK_NUMPAD4;
			break;
 
		case VK_RIGHT:
			if (!isE0)
				virtualKey = VK_NUMPAD6;
			break;
 
		case VK_UP:
			if (!isE0)
				virtualKey = VK_NUMPAD8;
			break;
 
		case VK_DOWN:
			if (!isE0)
				virtualKey = VK_NUMPAD2;
			break;
 
			// NUMPAD 5 doesn't have its e0 bit set
		case VK_CLEAR:
			if (!isE0)
				virtualKey = VK_NUMPAD5;
			break;
		}

		// a key can either produce a "make" or "break" scancode. this is used to differentiate between down-presses and releases
		// see http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
		const bool was_up = ((flags & RI_KEY_BREAK) != 0);
 
		// now sanitise the virtual key
		virtualKey &= 0xff;

		// check if key up or key down
		if(was_up)
		{
			// key up
			// set the bitmap state to key up
			uint32_t dword = virtualKey >> 5;
			uint32_t bit = virtualKey & 0x1f;
			this->KEYSTATES[dword] &= ~(1 << bit);

			// call the key up event if it exists
			if(this->key_released_event)
				(*key_released_event)(this, virtualKey);
		}
		else
		{
			// key down
			// get dword and bit in dword for keystate bitmap
			uint32_t dword = virtualKey >> 5;
			uint32_t bit = virtualKey & 0x1f;

			// check if the key is not already down
			if(!(this->KEYSTATES[dword] & (1 << bit)))
			{
				// set the bitmap state to key down
				this->KEYSTATES[dword] |= (1 << bit);
				
				// call the key down event if it exists
				if(this->key_pressed_event)
					(*key_pressed_event)(this, virtualKey);
			}
		}

	}

	else if (raw->header.dwType == RIM_TYPEMOUSE)
	{
		// get a pointer to the mouse data
		const RAWMOUSE& rawMS = raw->data.mouse;

		// get some values
		USHORT flags = rawMS.usFlags;
		USHORT buttons = rawMS.usButtonFlags;
		USHORT wheel = rawMS.usButtonData;

		// check mouse button states
		// buttons down
		if(this->key_pressed_event)
		{
			if(buttons & (RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_BUTTON_1_DOWN))
				(*key_pressed_event)(this, VK_LBUTTON);
			if(buttons & (RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_BUTTON_2_DOWN))
				(*key_pressed_event)(this, VK_RBUTTON);
			if(buttons & (RI_MOUSE_MIDDLE_BUTTON_DOWN | RI_MOUSE_BUTTON_3_DOWN))
				(*key_pressed_event)(this, VK_MBUTTON);
			if(buttons & RI_MOUSE_BUTTON_4_DOWN)
				(*key_pressed_event)(this, VK_XBUTTON1);
			if(buttons & RI_MOUSE_BUTTON_5_DOWN)
				(*key_pressed_event)(this, VK_XBUTTON2);
		}

		// buttons up
		if(this->key_released_event)
		{
			if(buttons & (RI_MOUSE_LEFT_BUTTON_UP | RI_MOUSE_BUTTON_1_UP))
				(*key_released_event)(this, VK_LBUTTON);
			if(buttons & (RI_MOUSE_RIGHT_BUTTON_UP | RI_MOUSE_BUTTON_2_UP))
				(*key_released_event)(this, VK_RBUTTON);
			if(buttons & (RI_MOUSE_MIDDLE_BUTTON_UP | RI_MOUSE_BUTTON_3_UP))
				(*key_released_event)(this, VK_MBUTTON);
			if(buttons & RI_MOUSE_BUTTON_4_UP)
				(*key_released_event)(this, VK_XBUTTON1);
			if(buttons & RI_MOUSE_BUTTON_5_UP)
				(*key_released_event)(this, VK_XBUTTON2);
		}

		// call the mouse event if necessary and if it exists
		if(this->mouse_event)
		{
			// get x, y values
			int last_x = rawMS.lLastX;
			int last_y = rawMS.lLastY;

			// calculate relative mouse position if absolute coords are given
			if(flags & MOUSE_MOVE_ABSOLUTE)
			{
				last_x = last_x - this->mouse_abs_x;
				last_y = last_y - this->mouse_abs_y;
				this->mouse_abs_x = rawMS.lLastX;
				this->mouse_abs_y = rawMS.lLastY;
			}

			// call the event handler if there is any change
			if(last_x || last_y || wheel)
				(*this->mouse_event)(this, last_x, last_y, (short)wheel);
		}
	}

	return;
}

//////////////////////////////////////////////////////////////
// input event handler registration
void Snowstorm::Window::RegisterKeyPressedEventHandler(void(*key_pressed_event_handler)(Window*, uint8_t))
{
	this->key_pressed_event = key_pressed_event_handler;
}

void Snowstorm::Window::RegisterKeyReleasedEventHandler(void(*key_released_event_handler)(Window*, uint8_t))
{
	this->key_released_event = key_released_event_handler;
}

void Snowstorm::Window::RegisterMouseEventHandler(void(*mouse_event_handler)(Window*, int, int, short))
{
	this->mouse_event = mouse_event_handler;
}
